package baidu.hudson.plugin.result.collector;

import baidu.hudson.plugin.result.collector.MessageRouter.MessageSender;
import baidu.hudson.plugin.result.collector.MessageRouter.MessageSenderBuilder;
import baidu.hudson.plugin.result.collector.utils.BuildAnalyzer;
import baidu.hudson.plugin.result.collector.utils.JsonUtils;
import baidu.hudson.plugin.result.collector.utils.LocalCache;
import hudson.Launcher;
import hudson.Util;
import hudson.model.*;
import hudson.model.labels.LabelAtom;
import hudson.scm.SubversionChangeLogSet;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Builder;
import hudson.triggers.SCMTrigger;
import hudson.triggers.TimerTrigger;
import hudson.util.TagCloud;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.kohsuke.stapler.DataBoundConstructor;
import org.springframework.jms.JmsException;
import org.springframework.jms.UncategorizedJmsException;

import java.io.IOException;
import java.net.ConnectException;
import java.util.*;
import java.util.concurrent.Future;

// add by yanshuai
import hudson.*;
import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import hudson.model.*;
import hudson.tasks.*;
import java.util.regex.*;
import javax.xml.parsers.*;
import java.net.URLEncoder;
import org.kohsuke.stapler.*;
import net.sf.json.JSONObject;
import java.text.SimpleDateFormat;
import hudson.util.FormValidation;
import javax.servlet.ServletException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import baidu.hudson.plugin.result.collector.utils.*;
// add by yanshuai

/**
 * Sample {@link Builder}.
 * <p/>
 * <p/>
 * there is the hudson publisher which will be execute after a build , in the publisher have some
 * process for transfer the result to the remote server .
 * <p/>
 * When a build is performed, the {@link #perform(AbstractBuild, Launcher, BuildListener)} method
 * will be invoked.
 *
 * @author Kohsuke Kawaguchi
 */
public class ResultPublisher extends Publisher {

    private String hudsonUrl;
    private String teamFoundation;
    private String icafeProject;
    private String icafeModule;
    private String icafeVersion;
    private String viewUrl;
    private String viewName;
    private String projectType;
    private String localServername;
    private boolean doSync;
    // add by yanshuai
    private String urlJobname;
    private String junitXml;
    private final String server = "db-testing-atd02.db01.baidu.com";
    private final int port = 8880;
    // add by yanshuai
    
    private final static String exceptionString = "error! sending failed , " +
            "it maybe caused by unreachable the remote server or other exception , " +
            "please ensure the config is correct , if the problem is still there , please contact the developer at yuxinlei@baidu.com .";
    private final static String jsonExceptionString = "error ! can't convert json string as : ";

    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public ResultPublisher(String localServername, String hudsonUrl, String teamFoundation, String hudsonSlave,
                           String icafeProject, String icafeModule, String icafeVersion, String viewUrl, String viewName,
                           String projectType, String projectUrl , boolean doSync
                           // add by yanshuai
                           ,String urlJobname, String junitXml
                           // add by yanshuai
                           ) {

        this.setHudsonUrl(hudsonUrl);
        this.setTeamFoundation(teamFoundation);
        this.setIcafeProject(icafeProject);
        this.setIcafeModule(icafeModule);
        this.setIcafeVersion(icafeVersion);
        this.setViewUrl(viewUrl);
        this.setViewName(viewName);
        this.setProjectType(projectType);
        this.setLocalServername(localServername);
        this.setDoSync(doSync);
        // add by yanshuai
        this.setUrlJobname(urlJobname);
        this.setJunitXml(junitXml);
        // add by yanshuai

    }

    // add by yanshuai
    public void setUrlJobname(String urlJobname)
    {
        this.urlJobname = urlJobname;
    }
    
    public String getUrlJobname()
    {
        return urlJobname;
    }
    
    public void setJunitXml(String junitXml)
    {
        this.junitXml = junitXml;
    }

    public String getjunitXml()
    {
        return junitXml;
    }

    private String htmldecode(String str)
    {
        str = str.replace("&quot;", "\"");
        str = str.replace("&lt;", "<");
        str = str.replace("&gt;", ">");
        str = str.replace("&amp;", "&");
        return str;
    }
    
    private NameValuePair[] merge(NameValuePair[] left, NameValuePair[] right)
    {
        int left_size = left.length;
        int right_size = right.length;
        NameValuePair[] nameValuePair = new NameValuePair[left_size + right_size];
        for (int i = 0; i < left_size; ++i)
        {
            nameValuePair[i] = left[i];
        }
        
        for (int i = 0; i < right_size; ++i)
        {
            nameValuePair[i + left_size] = right[i];
        }
        return nameValuePair;
    }
    
    private NameValuePair[] parseXml(String fileName)
    {
        NameValuePair[] nameValuePair = null;

        try
        {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.parse(fileName);
            NodeList moreNodeList = doc.getElementsByTagName("more");
            int size = moreNodeList.getLength();
            nameValuePair = new NameValuePair[size];
            for (int i = 0; i < size; ++i)
            {
                String name = htmldecode(moreNodeList.item(i).getAttributes().getNamedItem("name").getNodeValue());
                String value = htmldecode(moreNodeList.item(i).getAttributes().getNamedItem("value").getNodeValue());
                nameValuePair[i] = new NameValuePair(URLEncoder.encode(name, "UTF-8"), URLEncoder.encode(value, "UTF-8"));
            }
        }
        catch (Exception e)
        {
            System.out.println("Warning: no junit xml or junit xml has something wrong!");
        }
        
        return nameValuePair;
    }
    // add by yanshuai
    
    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException {
        // add by yanshuai
        AbstractProject job = build.getProject();
        String jobname = job.getName();
        int buildNum = build.getNumber();
        String hudsonHome = Hudson.getInstance().getRootDir().getPath();
        String jobHome = hudsonHome+"/jobs/"+jobname;
        String workspace = jobHome+"/builds/"+buildNum+"/archive";
        NameValuePair hudsonURLPair = new NameValuePair("hudsonURL", URLEncoder.encode(hudsonUrl, "UTF-8"));
        NameValuePair jobnamePair = new NameValuePair("jobname", URLEncoder.encode(jobname, "UTF-8"));
        NameValuePair buildNumPair = new NameValuePair("buildNum", URLEncoder.encode(String.valueOf(buildNum), "UTF-8"));
        
        HttpRequest httpRequest = new HttpRequest(server, port);
        if (urlJobname != null && urlJobname.compareTo(jobname) != 0)
        {
            NameValuePair urlJobnamePair = new NameValuePair("urlJobname", URLEncoder.encode(urlJobname, "UTF-8"));
            httpRequest.doPost("/hudmeasure/update_jobname.php", new NameValuePair[]{hudsonURLPair, jobnamePair, urlJobnamePair});
            urlJobname = jobname;
            job.save();
        }
        // add by yanshuai
        
        listener.getLogger().println("start to collect the build result to the remote server .");

        // this is where you 'build' the project
        // since this is a dummy, we just say 'hello world' and call that a build

        // this also shows how you can consult the global configuration of the builder
        listener.getLogger().println("parseing the data....");

        listener.getLogger().println("generate the json data....");
        String json = BuildAnalyzer.generateMappedJsonResult(build, this);
        listener.getLogger().println("finished parase .");

        listener.getLogger().println("start sending the data to the remote server at " + this.getDescriptor().getRemoteEndPoint());

        Map<String, String> result = new HashMap<String, String>();

        try {
            result = getResultFromRemoteRequest(json, listener);
            if (doSync && result != null && result.get("numbers") != null) {
                List<String> numberList = JsonUtils.convertJsonListString(result.get("numbers"));
                List<AbstractBuild> previousBuilds = build.getProject().getBuilds();
                List<String> buildsJson = generateSyncBuildsJsonList(previousBuilds, numberList);
                Thread.sleep(Long.parseLong(result.get("period")));
                sendOutBody(buildsJson, listener);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
            listener.getLogger().println("Error paras period from server" + result.get("period"));
        } catch (IOException e) {
            e.printStackTrace();
            listener.getLogger().println(jsonExceptionString + result.get("numbers"));
        }

        // add by yanshuai
        try
        {
            NameValuePair[] left = new NameValuePair[]{hudsonURLPair, jobnamePair, buildNumPair};
            NameValuePair[] right = parseXml(workspace+"/"+junitXml);
            if (right != null)
            {
                httpRequest.doPost("/hudmeasure/records.php", merge(left, right));
            }
            else
            {
                httpRequest.doPost("/hudmeasure/records.php", left);
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        // add by yanshuai
        
        return true;
    }


    private Map<String, String> getResultFromRemoteRequest(String json, BuildListener listener) {
        MessageSender messageSender = MessageSenderBuilder.createMessageSenderBuilder().createJmsComponent("jms", this.getDescriptor().getRemoteEndPoint()).build();
        CamelContext camelContext = messageSender.getContext();
        Map<String, String> result = new HashMap<String, String>();
        ProducerTemplate template = camelContext.createProducerTemplate();

        try {
            template.start();
            //template.sendBody("jms:queue:xmlBody",json);
            Future<Map> future = template.asyncRequestBody("jms:queue:xmlBody", json, Map.class);
            Boolean finished = false;
            while (!finished) {
                finished = future.isDone();
                if (!finished) {
                    Thread.sleep(50);
                }
            }
            result = future.get();
            listener.getLogger().println("sending in async callback model finished .");
            template.stop();
        } catch (UncategorizedJmsException exception) {
            exception.printStackTrace();
            listener.getLogger().println(exceptionString);
        } catch (JmsException exception) {
            exception.printStackTrace();
            listener.getLogger().println(exceptionString);
        } catch (Exception exception) {
            exception.printStackTrace();
            listener.getLogger().println(exceptionString);
        }

        return result;
    }


    private List<String> generateSyncBuildsJsonList(List<AbstractBuild> builds, List<String> numberList) {
        List<String> toBeSyncList = new ArrayList<String>();
        for (AbstractBuild build : builds) {
            if (!numberList.contains(String.valueOf(build.getNumber()))) {
                toBeSyncList.add(BuildAnalyzer.generateMappedJsonResult(build, this));
            }
        }
        return toBeSyncList;
    }


    private void sendOutBody(List<String> jsons, BuildListener listener) {
        MessageSender messageSender = MessageSenderBuilder.createMessageSenderBuilder().createJmsComponent("jms", this.getDescriptor().getRemoteEndPoint()).build();
        CamelContext camelContext = messageSender.getContext();
        Map<String, String> result = new HashMap<String, String>();
        ProducerTemplate template = camelContext.createProducerTemplate();

        try {
            template.start();
            //template.sendBody("jms:queue:xmlBody",json);
            template.sendBody("jms:queue:bulkXML", jsons);

            listener.getLogger().println("sending in async out only model finished finished .");
            template.stop();
        } catch (UncategorizedJmsException exception) {
            exception.printStackTrace();
            listener.getLogger().println(exceptionString);
        } catch (JmsException exception) {
            exception.printStackTrace();
            listener.getLogger().println(exceptionString);
        } catch (Exception e) {
            e.printStackTrace();
            listener.getLogger().println(exceptionString);
        }

    }


    // overrided for better type safety.
    // if your plugin doesn't really define any property on Descriptor,
    // you don't have to do this.
    @Override
    public ResultDescriptor getDescriptor() {
        return (ResultDescriptor) super.getDescriptor();
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.STEP;
    }


    public String getHudsonUrl() {
        return hudsonUrl;
    }

    public void setHudsonUrl(String hudsonUrl) {
        this.hudsonUrl = hudsonUrl;
    }

    public String getTeamFoundation() {
        return teamFoundation;
    }

    public void setTeamFoundation(String teamFoundation) {
        this.teamFoundation = teamFoundation;
    }

    public String getIcafeProject() {
        return icafeProject;
    }

    public void setIcafeProject(String icafeProject) {
        this.icafeProject = icafeProject;
    }

    public String getIcafeModule() {
        return icafeModule;
    }

    public void setIcafeModule(String icafeModule) {
        this.icafeModule = icafeModule;
    }

    public String getIcafeVersion() {
        return icafeVersion;
    }

    public void setIcafeVersion(String icafeVersion) {
        this.icafeVersion = icafeVersion;
    }

    public String getViewUrl() {
        return viewUrl;
    }

    public void setViewUrl(String viewUrl) {
        this.viewUrl = viewUrl;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public String getProjectType() {
        return projectType;
    }

    public void setProjectType(String projectType) {
        this.projectType = projectType;
    }

    public String getLocalServername() {
        return localServername;
    }

    public void setLocalServername(String localServername) {
        this.localServername = localServername;
    }

    public boolean isDoSync() {
        return doSync;
    }

    public void setDoSync(boolean doSync) {
        this.doSync = doSync;
    }
}

